using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Practices.ServiceLocation;
using SimpleInjector;
using ActivationException = Microsoft.Practices.ServiceLocation.ActivationException;

namespace Beaker.Injection.Desktop.SimpleInjector
{
	internal sealed class ContainerBuilder
		: IContainerBuilder
	{
		private readonly Container _container;
		private readonly IDictionary<Type, ISet<Registration>> _futureRegistrations = new Dictionary<Type, ISet<Registration>>();
		private readonly IDictionary<Type, Registration> _concreteRegistrations = new Dictionary<Type, Registration>();
		private IServiceLocator _locator;

		internal ContainerBuilder()
		{
			_container = new Container();
		}

		#region IContainerBuilder

		public void Register<TConcrete>() where TConcrete : class
		{
			Register(typeof(TConcrete));
		}

		public void Register<TService, TImplementation>()
			where TService : class
			where TImplementation : class, TService
		{
			Registration registration;
			if (_concreteRegistrations.TryGetValue(typeof(TImplementation), out registration))
				_container.AddRegistration(typeof(TService), registration);
			else
				_container.Register<TService, TImplementation>();
		}

		public void Register<TService>(Func<TService> instanceCreator) where TService : class
		{
			_container.Register<TService>(instanceCreator);
		}

		public void Register(Type concreteType)
		{
			Registration registration = Lifestyle.Transient.CreateRegistration(concreteType, _container);
			_concreteRegistrations[concreteType] = registration;

			_container.AddRegistration(concreteType, registration);
		}

		public void Register(Type serviceType, Type implementation)
		{
			Registration registration;
			if (_concreteRegistrations.TryGetValue(implementation, out registration))
				_container.AddRegistration(serviceType, registration);
			else
				_container.Register(serviceType, implementation);
		}

		public void Register(Type serviceType, Func<object> instanceCreator)
		{
			_container.Register(serviceType, instanceCreator);
		}

		public void RegisterSingleton<TConcrete>() where TConcrete : class
		{
			Registration registration = Lifestyle.Singleton.CreateRegistration<TConcrete>(_container);
			_concreteRegistrations[typeof(TConcrete)] = registration;

			_container.AddRegistration(typeof(TConcrete), registration);
		}

		public void RegisterSingleton<TService, TImplementation>()
			where TService : class
			where TImplementation : class, TService
		{
			Registration registration;
			if (_concreteRegistrations.TryGetValue(typeof(TImplementation), out registration) && registration.Lifestyle == Lifestyle.Singleton)
				_container.AddRegistration(typeof(TService), registration);
			else
				_container.RegisterSingle<TService, TImplementation>();
		}

		public void RegisterSingleton<TService>(TService instance) where TService : class
		{
			if (typeof(TService).IsInterface)
				_container.RegisterSingle<TService>(instance);
			else
			{
				// not an interface, so actual a concrete registration
				Registration registration = Lifestyle.Singleton.CreateRegistration<TService>(_container);
				_concreteRegistrations[typeof(TService)] = registration;

				_container.AddRegistration(typeof(TService), registration);
			}
		}

		public void RegisterSingleton<TService>(Func<TService> instanceCreator) where TService : class
		{
			if (typeof(TService).IsInterface)
				_container.RegisterSingle<TService>(instanceCreator);
			else
			{
				// not an interface, so actual a concrete registration
				Registration registration = Lifestyle.Singleton.CreateRegistration<TService>(instanceCreator, _container);
				_concreteRegistrations[typeof(TService)] = registration;

				_container.AddRegistration(typeof(TService), registration);
			}
		}

		public void RegisterSingleton(Type serviceType, Type implementation)
		{
			Registration registration;
			if (_concreteRegistrations.TryGetValue(implementation, out registration) && registration.Lifestyle == Lifestyle.Singleton)
				_container.AddRegistration(serviceType, registration);
			else
				_container.RegisterSingle(serviceType, implementation);
		}

		public void RegisterSingleton(Type serviceType, Func<object> instanceCreator)
		{
			if (serviceType.IsInterface)
				_container.RegisterSingle(serviceType, instanceCreator);
			else
			{
				// not an interface, so actual a concrete registration
				Registration registration = Lifestyle.Singleton.CreateRegistration(serviceType, instanceCreator, _container);
				_concreteRegistrations[serviceType] = registration;

				_container.AddRegistration(serviceType, registration);
			}
		}

		public void RegisterSingleton(Type serviceType, object instance)
		{
			if (serviceType.IsInterface)
				_container.RegisterSingle(serviceType, instance);
			else
			{
				// not an interface, so actual a concrete registration
				Registration registration = Lifestyle.Singleton.CreateRegistration(serviceType, _container);
				_concreteRegistrations[serviceType] = registration;

				_container.AddRegistration(serviceType, registration);
			}
		}

		public void RegisterAnother<TService, TImplementation>() where TService : class
		{
			RegisterAnother<TService>(typeof(TImplementation));
		}

		public void RegisterAnotherSingleton<TService, TImplementation>() where TService : class
		{
			RegisterAnotherSingleton<TService>(typeof(TImplementation));
		}

		public void RegisterAnother<TService>(Type implementationType) where TService : class
		{
			ISet<Registration> set;
			if (!_futureRegistrations.TryGetValue(typeof(TService), out set))
			{
				set = new HashSet<Registration>();
				_futureRegistrations.Add(typeof(TService), set);
			}

			Registration registration;
			if (!_concreteRegistrations.TryGetValue(implementationType, out registration))
			{
				registration = Lifestyle.Transient.CreateRegistration(implementationType, _container);
				_concreteRegistrations[implementationType] = registration;
			}

			set.Add(registration);
		}

		public void RegisterAnotherSingleton<TService>(Type implementationType) where TService : class
		{
			ISet<Registration> set;
			if (!_futureRegistrations.TryGetValue(typeof(TService), out set))
			{
				set = new HashSet<Registration>();
				_futureRegistrations.Add(typeof(TService), set);
			}

			Registration registration;
			if (!_concreteRegistrations.TryGetValue(implementationType, out registration))
			{
				registration = Lifestyle.Singleton.CreateRegistration(implementationType, _container);
				_concreteRegistrations[implementationType] = registration;
			}

			set.Add(registration);
		}

		public void RegisterDefault<TBase, TSpecific>()
			where TBase : class
			where TSpecific : class, TBase
		{
			Register<TBase>(_container.GetInstance<TSpecific>);
		}

		public object PhysicalContainer
		{
			get { return _container; }
		}

		#endregion
		#region ServiceLocator

		public void RegisterServiceLocator(IServiceLocator locator)
		{
			if (_locator != null)
				throw new InvalidOperationException("Cannot set service locator multiple times.");

			_locator = locator;

			var resolver = new ServiceLocatorResolver(locator);
			resolver.RegisterResolverOnContainer(_container);
		}

		#endregion
		#region Deferred

		/// <summary>
		/// Register all defered registrations, that where initially registered using RegisterAnother.
		/// </summary>
		internal void RegisterDeferredRegistrations()
		{
			foreach (KeyValuePair<Type, ISet<Registration>> kv in _futureRegistrations)
				_container.RegisterAll(kv.Key, kv.Value);
		}

		#endregion
	}

	internal sealed class ServiceLocatorResolver
	{
		private readonly IServiceLocator _locator;

		public ServiceLocatorResolver(IServiceLocator locator)
		{
			_locator = locator;
		}

		public void RegisterResolverOnContainer(Container container)
		{
			container.ResolveUnregisteredType += OnResolveUnregisteredType;
		}

		private void OnResolveUnregisteredType(object sender, UnregisteredTypeEventArgs unregisteredTypeEventArgs)
		{
			Contract.Assume(unregisteredTypeEventArgs.UnregisteredServiceType != null, "This should always be set by Simple Injector");
			Type type = unregisteredTypeEventArgs.UnregisteredServiceType;

			if (_locator == null)
				return;

			bool isEnumerable = false;

			if (type.IsGenericType)
			{
				Type typeDef = type.GetGenericTypeDefinition();
				if (typeDef == typeof(IEnumerable<>))
				{
					type = type.GetGenericArguments()[0];
					isEnumerable = true;
				}
			}

			try
			{
				if (isEnumerable)
				{
					IEnumerable<object> instances = _locator.GetAllInstances(type);
					if (instances != null && instances.Any())
					{
						unregisteredTypeEventArgs.Register(() => _locator.GetAllInstances(type));
						return;
					}
				}
				else
				{
					object instance = _locator.GetInstance(type);
					if (instance != null)
						unregisteredTypeEventArgs.Register(() => _locator.GetInstance(type));
				}
			}
			catch (ActivationException)
			{
			}
		}
	}
}